Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/#511 프론트의 토큰 만료 검증로직을 삭제하고 Axios를 도입한다. #554

Merged
merged 24 commits into from
Dec 19, 2023

Conversation

Creative-Lee
Copy link
Collaborator

@Creative-Lee Creative-Lee commented Nov 15, 2023

커밋 최대한 나눠 봤어요.
아래 낱개 커밋 링크 달아놓은 부분 확인해 주세요.
(네이밍같은 부분은 추후에 변경된 부분이 있기에 아래 커밋과 달라진 부분이 존재해요. files changed 로 최종확인 부탁드려요~)

📝주요 작업 내용

📝 서브 작업 내용

💬리뷰 참고사항

기존 코드에서 보장되던 최소한의 에러? 처리를 유지하였습니다.

  • 토큰 만료로 reissue 해야할 때
  • 리프레시 토큰까지 만료 되었을 때

노션 커스텀 에러 코드 모음집을 기준으로 토큰 만료, 재발급에 관련된 일반적인 상황은 해결이 됐으니,
나머지 특수 상황에 대한 부분을 에러바운더리로 해결하면 좋을 것 같아요.

#️⃣연관된 이슈

close #511

Copy link

github-actions bot commented Nov 15, 2023

Unit Test Results

  98 files    98 suites   10s ⏱️
390 tests 389 ✔️ 1 💤 0
395 runs  394 ✔️ 1 💤 0

Results for commit ea5e89b.

♻️ This comment has been updated with latest results.

@Creative-Lee Creative-Lee self-assigned this Nov 15, 2023
@Creative-Lee Creative-Lee added [ 🌞 FE ] 프론트엔드 크루들의 빛나는 개발 이야기 하나둘셋 호! 🔨 Refactor 꾸준한 개선이 더 나은 애플리케이션을 만든다 labels Nov 15, 2023
@Creative-Lee Creative-Lee changed the title Refactor/#511 Refactor/#511 프론트의 토큰 만료 검증로직을 삭제하고 Axios를 도입한다. Nov 15, 2023
Comment on lines +17 to +20
const params = new URLSearchParams();
params.append('keyword', query);
params.append('type', 'singer');
params.append('type', 'song');
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

axios는 params 값으로 URLSearchParams 인스턴스도 받아용
중복되는 param는 바로 위 remote 함수와 같은 방식으로 넣을 수 없기에 사용했습니다~

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

중복되는 param는 바로 위 remote 함수와 같은 방식으로 넣을 수 없기에 사용했습니다~

💬 안되는건가요? 아래와 같은 방법으로 충분히 되는 것 같아요.

// 1 string
const params = new URLSearchParams(`keyword=${query}&type=singer&type=song`);

// 2 array
const params = new URLSearchParams([
  ['keyword', query],
  ['type', 'singer'],
  ['type', 'song'],
]);

Copy link
Collaborator Author

@Creative-Lee Creative-Lee Dec 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

바로 위에 있는 remote함수와 같은 방식으로 넣을 수 없어서 URLSearchParams 를 썻다~ 가 핵심입니다 ㅋㅋ
제시해주신 방법도 충분히 가능합니다 ㅋㅋ

withCredentials: true,
};

const clientBasic = axios.create(defaultConfig);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클라이언트 베이식은 인터셉터를 달지 않은 인스턴스 입니다.
리프레쉬 remote 함수에만 사용됩니다.

리프레쉬 요청 함수에도 인터셉터를 부착한 인스턴스를 사용하는 경우,
응답 에러 인터셉터가 무한 호출됩니다.
이 경우 정상적으로 에러를 전파하기 위해 필요합니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 제가 이해한 게 맞나요? clientBasic 은 토큰 재발급 용도로만 사용되는 axios 인스턴스. 현재 인증 에러 시에 토큰을 재발급 받는 로직이 인터셉터로 주입하고 있지만, 토큰 재발급 요청 에러 시에는 재발급 요청을 반복해서 받을 필요가 없음. 그래서 토큰 재발급 요청에만 다른 인스턴스를 사용한다는 것.

Copy link
Collaborator Author

@Creative-Lee Creative-Lee Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

토큰 재발급 요청 에러 시에는 재발급 요청을 반복해서 받을 필요가 없음.
이부분 만 다시 설명하자면, 정확히는

에러 인터셉터가 달려있는 인스턴스로 리패치 요청 + 이 요청에서 에러가 발생하면 다시 인터셉터에 걸림 + 또 요청
위 무한 굴레를 쫌 쉽게 처리하려고 만들었어요!

(다른방향으로 해결방법을 생각해보면, 카운터 변수를 하나 만들어서 리패치 요청의 횟수를 제한해도 되겠네용)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금도 괜찮고, 다른 방향으로 제시한 것도 괜찮네요! 역시 도우밥!

const isAuthError = error.response?.status === 401;
const hasAuthorization = !!originalRequest?.headers.Authorization;

if (isAuthError && hasAuthorization) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

401 에러이면서, 토큰을 가지고 있었을 때, 즉 사용자의 토큰이 만료되었을 때를 특정합니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

401 에러 발생 시, 토큰을 가지고 있는 상황이라면 '토큰이 만료되었을 가능성'이 있다. 그러니 헤더에 토큰값이 있다면 재발급 요청을 보내라는 것이군요! 👍


if (isAuthError && hasAuthorization) {
try {
const { accessToken } = await (reissuePromise ??= reissue());
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

프라미스의 특성을 이용한 로직입니다.
한 페이지에서 auth 정보가 필요한 api를 2개 이상 호출하는데 토큰이 만료된 경우, reissue api는 1회만 호출해야하고 나머지 요청들 모두 순차적으로 재시도 해야합니다.

위 로직으로 조금이라도 먼저 호출된 api가 최초 1회만 reissue 요청을 보내고 뒤이어 실행되는 다수의 api는 해당 프라미스의 이행을 기다리게 됩니다.
이후 micro task queue에 inqueue한 순서대로 자기 자신을 재시도합니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reissue는 한번만 하는 로직을 만들기 어려웠을텐데 고생 많으셨네요! 하나의 프로미스로 관리한다는 점 인상 깊었습니다. 👍

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 예제를 만들어보니 이해가 되었네요.

let promise = null;

const reissue = () => {
  return new Promise((resolve) =>
    setTimeout(() => {
      console.log('promise');
      resolve('accessToken');
    }, 5000)
  );
};

const onExpired = async () => {
  const accessToken = await (promise ??= reissue());
  console.log(accessToken);
};

onExpired();
onExpired();
onExpired();
onExpired();

// reissue promise는 한 번만 이행되지만
// console.log(accessToken)은 여러번 실행되어서
// promise
// accessToken *4

Copy link
Collaborator

@ukkodeveloper ukkodeveloper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 도밥! 프로젝트 기간이 끝났음에도 계속 리팩터링하는 모습 멋집니다!
axios 도입하느라 고생 많으셨어요! 그리고 인증이 필요한 여러 요청 상황에서, promise를 하나로 관리하는 방법도 너무 멋있습니다!
작업단위가 많아서 axios 도입한 것 위주로 확인했어요! 커멘트 확인하고 답장 부탁드립니다!

}

export const postRefreshAccessToken = async (staleAccessToken: string) => {
const { data } = await clientBasic.post<RefreshAccessTokenRes>('/reissue', staleAccessToken);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 현재 해당 함수는 구현부에서 await 1번하고, 사용처에서 await를 1번 더 해요. 객체분해할당만 하는 것이기 때문에 함수 구현부 내부에서는 굳이 await를 하지 않아도 되지 않을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀하신 부분이 잘 이해가 안되어서 그런데
promise가 이행된 후에만 새 변수에 할당이 가능하지 않나요?
구조분해할당만을 위한 함수 구현부 내부의 await가 없어도 된다는 말씀이 맞을까용?

Copy link
Collaborator

@ukkodeveloper ukkodeveloper Nov 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구조분해할당만을 위한 함수 구현부 내부의 await가 없어도 된다는 말씀이 맞을까용?

이것은 이제 도밥이 판단해야 할 것 같아요! 정말 해당함수에서 구조분해할당을 해서 data를 가져와야 하는지요! 다만, 제가 말씀드리고 싶은 것은 그렇게 함으로써 async-await를 두 번 사용하게 된다는 점입니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 '구조분해 할당을 위해 await가 필요없다' 가 아니고,
'await + 구조분해 할당을 해서 가져와야 하는가~ 가' 주안점이었군요!

오 그럼 궁금한게 있는데, await가 2번 사용되면 단점이있나요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ukkodeveloper

바깥에서 data({ asccessToken })를 바로 쓰려면 data를 풀어내서 내보내야 됩니다.
그냥 내보내게 되면 response 객체이겠죠.

const { accessToken } = await getAccessToken(platform, code);

다만 async/await가 불편하다면 then이 붙은 promise를 내보내면 될 것 같네요.
둘의 차이는 어떨지 더 알아봐야 알 것 같은데, 똑똑한 여러분들이 알려주실거라 믿습니다 😄

export const getAccessToken = (platform: string, code: string) => {
  return client
    .get<AccessTokenRes>(`/login/${platform}`, {
      params: { code },
    })
    .then((response) => response.data);
};

withCredentials: true,
};

const clientBasic = axios.create(defaultConfig);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 제가 이해한 게 맞나요? clientBasic 은 토큰 재발급 용도로만 사용되는 axios 인스턴스. 현재 인증 에러 시에 토큰을 재발급 받는 로직이 인터셉터로 주입하고 있지만, 토큰 재발급 요청 에러 시에는 재발급 요청을 반복해서 받을 필요가 없음. 그래서 토큰 재발급 요청에만 다른 인스턴스를 사용한다는 것.

const isAuthError = error.response?.status === 401;
const hasAuthorization = !!originalRequest?.headers.Authorization;

if (isAuthError && hasAuthorization) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

401 에러 발생 시, 토큰을 가지고 있는 상황이라면 '토큰이 만료되었을 가능성'이 있다. 그러니 헤더에 토큰값이 있다면 재발급 요청을 보내라는 것이군요! 👍

} catch (error) {
window.alert('세션이 만료되었습니다. 다시 로그인 해주세요');
localStorage.removeItem('userToken');
window.location.href = '/login';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reissue에서 에러를 throw하고 리액트 단에서 라우팅해야할 것 같아요! 이거는 예외처리 (에러 바운더리)하면서 고려해보죠! 그리고 alert 부분도 로직과 ui가 결합되어 있는데 이것도 예외처리하면서 풀어낼 수 있을 것 같아요.

Copy link
Collaborator Author

@Creative-Lee Creative-Lee Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞아용. 저도 에러바운더리에서 해결하는 방법도 있다고 생각했어용.
실제로 깔끔하게 적용까지 하고 싶었는데... 에러 처리는 원래 코난하고 같이하기로 했던 부분이라, 에러바운더리 하나 만드는것도 조금 고민되더라고요. 그래서 기존 코드에서 에바 없이 해결했던 방법으로 그대로 구현해두었습니당.
(서비스 터지지만 않게요 ㅋㅋ)

에바 얼른 합시당 ! 으아아아아아아악!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅎㅎ 에바 고우고우

const clientBasic = axios.create(defaultConfig);
const client = axios.create(defaultConfig);

let reissuePromise: Promise<AccessTokenRes> | null = null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reissue 인터셉터 따로 모듈 분리하는 게 좋을 것 같아요. reissue에만 사용되는 변수인데 핵심 로직과 떨어져 있어서 계속 앞뒤로 왔다갔다하면서 읽게 되네요!

Copy link
Collaborator Author

@Creative-Lee Creative-Lee Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reissue에만 사용되는 변수인데 핵심 로직과 떨어져 있어서 계속 앞뒤로 왔다갔다하면서 읽게 되네요!

좋습니당~ 공감하는 부분이라 사용처와 가깝게 let 변수의 line을 변경했어용!(반영 완)

💬 다만, 인터셉터는 인스턴스에 부착하는 로직인 만큼 한 파일에 모아두는게 더 좋다고 생각하는데, 어떻게 생각하세용?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그냥 제 생각을 말할게요! ( 이렇게 해달라는 것은 아닙니다! 도밥이 생각하고 정리하면 좋을 것 같아요! )
우선 axios 인스턴스에 인터셉터를 주입하는 인터페이스를 보면 인자로 함수를 받잖아요. 이것만 봐도 요청 전후의 작업을 "독립적"으로 코드를 짤 수밖에 없도록 하는 느낌이 들거든요! "독립적"이라는 것은 별도로 분리해서 모듈화하거나 단위 테스트하거나 등등 장점(결국 관심사 분리를 통한 장점일 것 같네요)이 많잖아요. 그렇기 때문에 저는 분리하는 게 좋겠다는 생각이 들었어요!

별개로 만약 도밥이하나의 역할이라 결정 지었다면, reissuePromise 변수를 쓰는 함수 근처에 선언하는 게 더 좋을 것 같아요! 그래야위아래로 왔다갔다 안 하고 좋을 것 같습니다 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 좋은 접근법이네요.
인터페이스를 보고 생각하는 법 배워갑니다~! 야무진 인사이트 감사합니다.


originalRequest.headers.Authorization = `Bearer ${accessToken}`;

return client(originalRequest);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분 설명해주실 수 있나요? 본 요청을 다시 보내는 로직인가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞아용. axios는
client.get, client.post 형태의 인터페이스로 요청을 보낼 수도 있고
client(config객체) 형태의 인터페이스로도 요청을 보낼 수 있어용

기존 config객체에 토큰 받아온걸 set하고, 다시 요청 보내는 로직입니당
위 promise 사용처 설명처럼 순서대로 재요청해용~

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와!우! 신기하네요! 정말 좋군요 axios~


if (isAuthError && hasAuthorization) {
try {
const { accessToken } = await (reissuePromise ??= reissue());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reissue는 한번만 하는 로직을 만들기 어려웠을텐데 고생 많으셨네요! 하나의 프로미스로 관리한다는 점 인상 깊었습니다. 👍


throw error;
} finally {
reissuePromise = null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 reissuePromise를 응답 에러 인터셉터에서만 관리하는 것이 어떠한가요? 두 함수 모두 해당 변수를 조작할 필요는 없을 것 같다는 생각이 드네요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

논리적으로
리프레시 완료 후 null할당 vs 토큰 갈아끼고 재시도 전 null 할당의 차이였는데
로직 상 문제가 없네요ㅎㅎ 구현 당시에 착각했었나 봅니다 하하
아주 좋은 의견 감사용 반영했습니당 👍

Copy link
Collaborator

@cruelladevil cruelladevil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

타입에 관한 코멘트는 크게 신경쓰지 않아도 됩니당~
작업한 것에 대해서 동작하는지 확인할 수 있는 부분이 있으면 좋겠습니다ㅠ

@@ -1,5 +1,7 @@
/* eslint-disable prefer-const */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 prefer-consterror가 아니라 warning인데 disable한 이유가 있으신가요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

몰랐네요 ㅋㅋㅋㅋ 수정할게요~

@@ -0,0 +1,3 @@
export interface AccessTokenRes {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Res가 Response인가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 맞아요 Res, Req!


const { BASE_URL } = process.env;

const defaultConfig: AxiosRequestConfig = {
Copy link
Collaborator

@cruelladevil cruelladevil Dec 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 create는 CreateAxiosDefaults 타입을 받던데, AxiosRequestConfig 타입으로 선언한 이유가 있을까요?

export interface AxiosStatic extends AxiosInstance {
  create(config?: CreateAxiosDefaults): AxiosInstance;
  // ...
}

export interface CreateAxiosDefaults<D = any> extends Omit<AxiosRequestConfig<D>, 'headers'> {
  headers?: RawAxiosRequestHeaders | AxiosHeaders | Partial<HeadersDefaults>;
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 감사합니다. 몰랐어요 ㅋㅋ..
내부적으로 headers 를 오밋해서 갈아끼네요. 원래 의도대로 쓰는게 좋겠어요.
수정완!

Comment on lines -22 to +23
const { data: likes } = useFetch<LikeKillingPart[]>(getLikeParts);
const { data: myParts } = useFetch<LikeKillingPart[]>(getMyParts);
const { data: likes } = useFetch(getLikeParts);
const { data: myParts } = useFetch(getMyParts);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 제네릭이 필요 없어졌다면 useFetch도 수정해야되지 않을까요?

Copy link
Collaborator Author

@Creative-Lee Creative-Lee Dec 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떤 식으로 수정이 되어야할까요? 생각나는 방향이 있으시면 같이 알려주세용
제네릭 없이도 useFetch data 타입이 추론 될지 모르겠네용

Comment on lines +3 to 5
export const deleteMember = async (memberId: number) => {
await client.delete(`/members/${memberId}`);
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 async/await여야 하는 이유가 있나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useMutaion의 타입을 맞추려다보니 그렇습니다~
useMutaion의 타입을 어떻게 수정해볼 수 있을까요?

Comment on lines 3 to 5
export const putKillingPartLikes = async (songId: number, partId: number, likeStatus: boolean) => {
return await fetcher(`/songs/${songId}/parts/${partId}/likes`, 'PUT', { likeStatus });
await client.put(`/songs/${songId}/parts/${partId}/likes`, { likeStatus });
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 mutation 함수를 확인해보니 타입이 (...params: P) => Promise<T>인데, promise를 반환 안해도 되는건가요?

export const useMutation = <T, P extends any[]>(mutateFn: (...params: P) => Promise<T>) => {
  const [data, setData] = useState<T | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  // ...

}

export const postRefreshAccessToken = async (staleAccessToken: string) => {
const { data } = await clientBasic.post<RefreshAccessTokenRes>('/reissue', staleAccessToken);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ukkodeveloper

바깥에서 data({ asccessToken })를 바로 쓰려면 data를 풀어내서 내보내야 됩니다.
그냥 내보내게 되면 response 객체이겠죠.

const { accessToken } = await getAccessToken(platform, code);

다만 async/await가 불편하다면 then이 붙은 promise를 내보내면 될 것 같네요.
둘의 차이는 어떨지 더 알아봐야 알 것 같은데, 똑똑한 여러분들이 알려주실거라 믿습니다 😄

export const getAccessToken = (platform: string, code: string) => {
  return client
    .get<AccessTokenRes>(`/login/${platform}`, {
      params: { code },
    })
    .then((response) => response.data);
};

Comment on lines +17 to +20
const params = new URLSearchParams();
params.append('keyword', query);
params.append('type', 'singer');
params.append('type', 'song');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

중복되는 param는 바로 위 remote 함수와 같은 방식으로 넣을 수 없기에 사용했습니다~

💬 안되는건가요? 아래와 같은 방법으로 충분히 되는 것 같아요.

// 1 string
const params = new URLSearchParams(`keyword=${query}&type=singer&type=song`);

// 2 array
const params = new URLSearchParams([
  ['keyword', query],
  ['type', 'singer'],
  ['type', 'song'],
]);


if (isAuthError && hasAuthorization) {
try {
const { accessToken } = await (reissuePromise ??= reissue());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 예제를 만들어보니 이해가 되었네요.

let promise = null;

const reissue = () => {
  return new Promise((resolve) =>
    setTimeout(() => {
      console.log('promise');
      resolve('accessToken');
    }, 5000)
  );
};

const onExpired = async () => {
  const accessToken = await (promise ??= reissue());
  console.log(accessToken);
};

onExpired();
onExpired();
onExpired();
onExpired();

// reissue promise는 한 번만 이행되지만
// console.log(accessToken)은 여러번 실행되어서
// promise
// accessToken *4

@Creative-Lee Creative-Lee merged commit 7a7d161 into main Dec 19, 2023
4 checks passed
@Creative-Lee Creative-Lee deleted the refactor/#511 branch December 19, 2023 01:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[ 🌞 FE ] 프론트엔드 크루들의 빛나는 개발 이야기 하나둘셋 호! 🔨 Refactor 꾸준한 개선이 더 나은 애플리케이션을 만든다
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

[REFACTOR] 프론트의 토큰 만료 검증로직을 삭제하고 Axios를 도입한다.
3 participants